package com.hhj.demo.springmvc;

import com.hhj.demo.springmvc.annotations.*;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

/**
 * 手写mvc
 *
 * @author KING
 * @date 2018/10/14 18:04
 */
public class MyDispatcherServlet extends HttpServlet {
    private Properties properties = new Properties();
    private List<String> classNameList = new ArrayList<>();
    private Map<String, Object> beansMap = new HashMap<>();
    private Map<String, Object> urlMappingMap = new HashMap<>();

    private String propertiesPath = "config.properties";
    private String basePackage = "spring.base.package";
    private String defaultBasePackage = "com.hhj";

    @Override
    public void init(ServletConfig config) throws ServletException {
        initProperties();
        doScan(properties.getProperty(basePackage, defaultBasePackage));
        newInstance();
        doAutowired();
        urlMapping();
    }

    /**
     * 初始化配置文件
     */
    private void initProperties() {
        System.out.println("初始化配置文件");
        try {
            // 使用ClassLoader加载properties配置文件生成对应的输入流
            InputStream in = MyDispatcherServlet.class.getClassLoader().getResourceAsStream(propertiesPath);
            // 使用properties对象加载输入流
            properties.load(in);
            //获取key对应的value值
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取映射路径urlMappingMap
     */
    private void urlMapping() {
        System.out.println("获取映射路径urlMappingMap");
        for (Map.Entry<String, Object> beanMap : beansMap.entrySet()) {
            Object instance = beanMap.getValue();
            Class<?> instanceClass = instance.getClass();
            if (instanceClass.isAnnotationPresent(KingController.class)) {
                KingRequestMapping kingRequestMappingClass = instanceClass.getAnnotation(KingRequestMapping.class);
                String classMappingKey = kingRequestMappingClass.value();
                Method[] methods = instanceClass.getMethods();
                for (Method method : methods) {
                    if (method.isAnnotationPresent(KingRequestMapping.class)) {
                        KingRequestMapping kingRequestMapping = method.getAnnotation(KingRequestMapping.class);
                        String methodMappingKey = kingRequestMapping.value();
                        String urlPath = classMappingKey + (methodMappingKey.startsWith("/") ? "" : "/") + methodMappingKey;
                        urlMappingMap.put(urlPath, method);
                    }
                }
            }
        }
    }

    /**
     * 自动注入bean
     */
    private void doAutowired() {
        System.out.println("自动注入bean");
        for (Map.Entry<String, Object> beanMap : beansMap.entrySet()) {
            Object instance = beanMap.getValue();
            Class<?> instanceClass = instance.getClass();
            if (instanceClass.isAnnotationPresent(KingController.class)) {
                Field[] fields = instanceClass.getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(KingAutowired.class)) {
                        KingAutowired kingAutowired = field.getAnnotation(KingAutowired.class);
                        String kingAutowiredKey = kingAutowired.value();
                        Object valueBean = beansMap.get(kingAutowiredKey);
                        field.setAccessible(true);
                        try {
                            field.set(instance, valueBean);
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    } else {
                        continue;
                    }
                }
            } else {
                continue;
            }
        }
    }

    /**
     * 实例化所有扫描到的类
     */
    private void newInstance() {
        System.out.println("实例化所有扫描到的类");
        try {
            for (String className : classNameList) {
                Class<?> clazz = Class.forName(className.replace(".class", ""));
                if (clazz.isAnnotationPresent(KingController.class)) {
                    Object newInstance = clazz.newInstance();
                    KingRequestMapping kingRequestMapping = clazz.getAnnotation(KingRequestMapping.class);
                    String key = kingRequestMapping.value();
                    beansMap.put(key, newInstance);
                } else if (clazz.isAnnotationPresent(KingServer.class)) {
                    Object newInstance = clazz.newInstance();
                    KingServer kingRequestMapping = clazz.getAnnotation(KingServer.class);
                    String key = kingRequestMapping.value();
                    beansMap.put(key, newInstance);
                } else {
                    continue;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 扫描所有注解类
     *
     * @param basePackage
     */
    private void doScan(String basePackage) {
        System.out.println("扫描所有注解类");
        String tempPath = "/" + basePackage.replaceAll("\\.", "/");
        URL url = this.getClass().getClassLoader().getResource(tempPath);
        String fileStr = url.getFile();
        File file = new File(fileStr);
        try {
            String[] fileList = file.list();


            for (String path : fileList) {
                File filePath = new File(fileStr + path);
                if (filePath.isDirectory()) {
                    doScan(basePackage + "." + path);
                } else {
                    classNameList.add(basePackage + "." + filePath.getName());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("==========doPost===========");
        String uri = req.getRequestURI();
        String stringContextPath = req.getContextPath();
        String path = uri.replace(stringContextPath, "");

        Method method = (Method) urlMappingMap.get(path);
        if (method == null) {
            return;
        }
        String beansMapKey = "/" + path.split("/")[1];
        Object instance = beansMap.get(beansMapKey);
        Object[] reqParam = getParam(req, resp, method);
        try {
            method.invoke(instance, reqParam);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取url的参数
     *
     * @param req
     * @param resp
     * @param method
     * @return
     */
    private Object[] getParam(HttpServletRequest req, HttpServletResponse resp, Method method) {
        Class<?>[] clazzs = method.getParameterTypes();
        Object[] orgs = new Object[clazzs.length];
        int orgs_i = 0;
        int index = 0;
        for (Class<?> param : clazzs) {
            if (ServletRequest.class.isAssignableFrom(param)) {
                orgs[orgs_i++] = req;
            }
            if (ServletResponse.class.isAssignableFrom(param)) {
                orgs[orgs_i++] = resp;
            }
            Annotation[] annotations = method.getParameterAnnotations()[index];
            if (annotations.length > 0) {
                for (Annotation annotation : annotations) {
                    if (KingRequestParam.class.isAssignableFrom(annotation.getClass())) {
                        orgs[orgs_i++] = req.getParameter(((KingRequestParam) annotation).value());
                    }
                }
            }
            index++;
        }
        return orgs;
    }
}
